// Board.cpp
//
// Created By: Jeremy Butler
// Created On: 31-Mar-2011

#include "Board.h"
#include "ChessUtils.h"

#include <sstream>
#include <boost/concept_check.hpp>
#include <cassert>
#include <typeinfo>
using namespace std;

Board::Board() : whiteKingLocation(-1,-1), blackKingLocation(-1,-1),
		 whitePieces(), blackPieces(){
  Init();
}

void Board::Clear(){
  for(int i = 0; i < 8; i++){
    for(int j = 0; j < 8; j++){
      if(spaces[i][j] != NULL){
	delete spaces[i][j];
	spaces[i][j] = NULL;
      }
    }
  }
  whitePieces.clear();
  blackPieces.clear();
  whiteKingLocation.Set(-1,-1);
  blackKingLocation.Set(-1,-1);
}

void Board::Init(){
  for(int i = 0; i < 8; i++){
    for(int j = 0; j < 8; j++){
      spaces[i][j] = NULL;
    }
  }
}

Board::Board(const Board & other) : whiteKingLocation(other.whiteKingLocation),
				    blackKingLocation(other.blackKingLocation),
				    whitePieces(),
				    blackPieces(){
  Init();
  Copy(other);
}

void Board::Copy(const Board & other){
  Clear();
  for(int i = 0; i < 8; i++){
    for(int j = 0; j < 8; j++){
      Piece * pPiece = other.spaces[i][j];
      if(pPiece != NULL){
	AddPiece(*pPiece,i,j);
      }
    }
  }
}

Board::~Board(){
  Clear();
}

Board & Board::operator = (const Board & other){
  if(this != &other){
    Copy(other);    
  }
  return *this;
}

const Piece * Board::operator() (int row, int col) const{
  if(row < 0 || row > 7 || col < 0 || col > 7){
    stringstream error;
    error << "(" << row << "," << col << ") is not a valid board location";
    throw InvalidArgumentException(error.str());
  }
  
  return spaces[row][col];
}

void Board::MovePiece(int fromRow, int fromCol, int toRow, int toCol, PieceType newPieceType){
  Piece * pieceTaken = spaces[toRow][toCol];
  if(pieceTaken != NULL){
    if(pieceTaken->GetColor() == WHITE){
      set<Piece *>::iterator it = whitePieces.begin();
      while( it != whitePieces.end() ){
	if (*it == pieceTaken){
	  whitePieces.erase( it++ );
	  break;
	}
	else
	  ++it;
      }
    }
    else if(pieceTaken->GetColor() == BLACK){
      set<Piece *>::iterator it = blackPieces.begin();
      while( it != blackPieces.end() ){
	if (*it == pieceTaken){
	  blackPieces.erase( it++ );
	  break;
	}
	else
	  ++it;
      }
    }
    delete pieceTaken;
  }
  spaces[toRow][toCol] = spaces[fromRow][fromCol];
  spaces[fromRow][fromCol] = NULL;
  
  if(spaces[toRow][toCol]->GetType() == KING){
    if(spaces[toRow][toCol]->GetColor() == WHITE)
      whiteKingLocation.Set(toCol,toRow);
    else
      blackKingLocation.Set(toCol,toRow);
  }
}

void Board::AddPiece(const Piece & piece, int row, int col){
  RemovePiece(row,col);
  
  spaces[row][col] = piece.Clone();

  if(spaces[row][col]->GetColor() == WHITE)
    whitePieces.insert(spaces[row][col]);
  else if(spaces[row][col]->GetColor() == BLACK)
    blackPieces.insert(spaces[row][col]);
  
  if(spaces[row][col]->GetType() == KING){
    if(spaces[row][col]->GetColor() == WHITE)
      whiteKingLocation.Set(col,row);
    else
      blackKingLocation.Set(col,row); 
  }
}

set<Location> Board::GetValidMoves(int row, int col) const{
  set<Location> validMoves;
  if(spaces[row][col] != NULL)
    validMoves = spaces[row][col]->GetValidMoves(*this, row, col);
  return validMoves;
}

bool Board::IsInCheck(Color color) const{
  assert(whiteKingLocation.GetX() > -1 && whiteKingLocation.GetY() > -1);
  assert(blackKingLocation.GetX() > -1 && blackKingLocation.GetY() > -1);

  Location kingLocation((color == WHITE) ? whiteKingLocation : blackKingLocation);
  int row = kingLocation.GetY();
  int col = kingLocation.GetX();
  
  if(  InCheckByPawn(color, row, col)
    || InCheckByRook(color, row, col)
    || InCheckByKnight(color, row, col)
    || InCheckByBishop(color, row, col)
    || InCheckByQueen(color, row, col)
    || InCheckByKing(color, row, col))
    return true;
  
  return false;
}

bool Board::IsInCheckMate(Color color) const{
  return HaveNoMovesLeft(color);
}

bool Board::HaveNoMovesLeft(Color color) const{
  set<Location> moves;
  for(int i = 0; i < 8; i++){
    for(int j = 0; j < 8; j++){
      if(spaces[i][j] != NULL && spaces[i][j]->GetColor() == color){
	set<Location> tempMoves = spaces[i][j]->GetValidMoves(*this,i,j);
	for(set<Location>::iterator it = tempMoves.begin(); it != tempMoves.end(); it++){
	  moves.insert(*it);
	}
      }
    }
  }
  if(moves.empty())
    return true;

  return false;
}

bool Board::IsStaleMate(Color currentColor) const{
  return (HaveNoMovesLeft(currentColor) && !IsInCheck(currentColor));
}

bool Board::CheckSameTypeInMoves(const set<Location> & moves, const Piece * piece) const{
  Piece * temp;
  for(set<Location>::iterator it = moves.begin(); it != moves.end(); it++){
    temp = spaces[it->GetY()][it->GetX()];
    if(temp != NULL && temp->GetColor() != piece->GetColor() && temp->GetType() == piece->GetType())
      return true;
  }
  return false;
}

void Board::RemoveCheckMoves(set<Location> & moves, int fromRow, int fromCol) const{
  Board tempBoard(*this);
  int toRow, toCol;
  Color color = tempBoard.spaces[fromRow][fromCol]->GetColor();

  set<Location>::iterator it = moves.begin();
  while( it != moves.end()){
    
    toRow = it->GetY();
    toCol = it->GetX();
    Piece * temp = tempBoard.spaces[toRow][toCol];
    tempBoard.spaces[toRow][toCol] = tempBoard.spaces[fromRow][fromCol];
    tempBoard.spaces[fromRow][fromCol] = NULL;

    if(tempBoard.spaces[toRow][toCol]->GetType() == KING){
      if(color == WHITE)
	tempBoard.whiteKingLocation.Set(toCol,toRow);
      else if(color == BLACK)
	tempBoard.blackKingLocation.Set(toCol,toRow);
    }

    
    if(tempBoard.IsInCheck(color))
      moves.erase(it++);
    else
      ++it;
    
    tempBoard.spaces[fromRow][fromCol] = tempBoard.spaces[toRow][toCol];
    tempBoard.spaces[toRow][toCol] = temp;
  }
}

const set<Piece *> & Board::GetPieces(Color color) const{
  if(color == WHITE)
    return whitePieces;
  else if(color == BLACK)
    return blackPieces;
}

const Location Board::GetPieceLocation(const Piece * piece) const{
  Location location(-1,-1);
  for(int i = 0; i < 8; i++){
    for(int j = 0; j < 8; j++){
      if(spaces[i][j] == piece){
	location.Set(j,i);
	return location;
      }
    }
  }
  return location;
}

void Board::RemovePiece(int row, int col){
  Piece * pieceTaken = spaces[row][col];
  if(pieceTaken != NULL){
    if(pieceTaken->GetColor() == WHITE){
      set<Piece *>::iterator it = whitePieces.begin();
      while( it != whitePieces.end() ){
	if (*it == pieceTaken){
	  whitePieces.erase( it++ );
	  break;
	}
	else
	  ++it;
      }
    }
    else if(pieceTaken->GetColor() == BLACK){
      set<Piece *>::iterator it = blackPieces.begin();
      while( it != blackPieces.end() ){
	if (*it == pieceTaken){
	  blackPieces.erase( it++ );
	  break;
	}

	else
	  ++it;
      }
    }
    delete pieceTaken;
  }
}

bool Board::InCheckByPawn(Color color, int kingRow, int kingCol) const{
  set<Location> moves;

  Piece * tempPiece = new Pawn(color);
  moves = tempPiece->GetValidMoves(*this,kingRow,kingCol,true);
  if(CheckSameTypeInMoves(moves,tempPiece)){
    delete tempPiece;
    return true;
  }
  delete tempPiece;
  return false;
}
bool Board::InCheckByRook(Color color, int kingRow, int kingCol) const{
  set<Location> moves;

  Piece * tempPiece = new Rook(color);
  moves = tempPiece->GetValidMoves(*this,kingRow,kingCol,true);
  if(CheckSameTypeInMoves(moves,tempPiece)){
    delete tempPiece;
    return true;
  }
  delete tempPiece;
  return false;
}
bool Board::InCheckByKnight(Color color, int kingRow, int kingCol) const{
  set<Location> moves;

  Piece * tempPiece = new Knight(color);
  moves = tempPiece->GetValidMoves(*this,kingRow,kingCol,true);
  if(CheckSameTypeInMoves(moves,tempPiece)){
    delete tempPiece;
    return true;
  }
  delete tempPiece;
  return false;
}
bool Board::InCheckByBishop(Color color, int kingRow, int kingCol) const{
  set<Location> moves;

  Piece * tempPiece = new Bishop(color);
  moves = tempPiece->GetValidMoves(*this,kingRow,kingCol,true);
  if(CheckSameTypeInMoves(moves,tempPiece)){
    delete tempPiece;
    return true;
  }
  delete tempPiece;
  return false;
}
bool Board::InCheckByQueen(Color color, int kingRow, int kingCol) const{
  set<Location> moves;

  Piece * tempPiece = new Queen(color);
  moves = tempPiece->GetValidMoves(*this,kingRow,kingCol,true);
  if(CheckSameTypeInMoves(moves,tempPiece)){
    delete tempPiece;
    return true;
  }
  delete tempPiece;
  return false;
}
bool Board::InCheckByKing(Color color, int kingRow, int kingCol) const{
  set<Location> moves;

  Piece * tempPiece = new King(color);
  moves = tempPiece->GetValidMoves(*this,kingRow,kingCol,true);
  if(CheckSameTypeInMoves(moves,tempPiece)){
    delete tempPiece;
    return true;
  }
  delete tempPiece;
  return false;
}
